﻿namespace Jsl.FxCop
{
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Searches for overrides of the Dispose method that don't call base.Dispose.
    /// </summary>
    public class CallBaseDispose : BaseIntrospectionRule
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="CallBaseDispose"/> class.
        /// </summary>
        public CallBaseDispose()
            : base(typeof(CallBaseDispose).Name, "Jsl.FxCop.Rules", typeof(CallBaseDispose).Assembly)
        {
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Checks the specified member.
        /// </summary>
        /// <param name="member">The member to check.</param>
        /// <returns>The problems found if any.</returns>
        public override ProblemCollection Check(Member member)
        {
            member.DoIfVirtualDisposeMethod(
                method =>
                {
                    // If this is the original virtual method then it doesn't have to call base so exit.
                    var overriddenMethod = method.OverriddenMethod;
                    if (overriddenMethod != null)
                    {
                        // Search for the call to base.Dispose(bool).
                        var baseDisposeCalls =
                            from instruction in method.Instructions
                            where (instruction.OpCode == OpCode.Call) || (instruction.OpCode == OpCode.Callvirt)
                            let calledMethod = (Method)instruction.Value
                            where calledMethod.IsVirtualDisposeMethod()
                                && (calledMethod.DeclaringType == overriddenMethod.DeclaringType)
                            select instruction;

                        // If no base.Dispose(bool) was called then add the problem.
                        if (!baseDisposeCalls.Any())
                        {
                            this.Problems.Add(new Problem(this.GetResolution(), method.SourceContext));
                        }
                    }
                });

            return this.Problems;
        }
        #endregion Public Methods
    }
}